﻿using System;
using System.Collections.Generic;
using System.Text;
using ClieOp.Record;

namespace ClieOp
{
    /// <summary>
    /// A ClieOp Bestand contains one or several batches. A batch is a group of items from the same ordering party.
    /// </summary>
    [Serializable]
    public class Batch
    {
        private const string Aanleveringsmuntsoort = "EUR";
        private const long Nawcode = 1;
        private const string Testcode = "P";
        private readonly long _gewensteverwerkingsdatum;
        private readonly string _naamopdrachtgever;
        private readonly List<Post> _posten = new List<Post>();
        private readonly long _rekeningnummeropdrachtgever;
        private readonly List<string> _vasteomschrijvingen = new List<string>();

        /// <summary>
        /// Create a new ClieOp batch.
        /// </summary>
        /// <param name="rekeningnummeropdrachtgever">Account number ordering party.</param>
        /// <param name="gewensteverwerkingsdatum">Desired processing date.</param>
        /// <param name="naamopdrachtgever">Name ordering party.</param>
        public Batch(long rekeningnummeropdrachtgever, DateTime gewensteverwerkingsdatum, string naamopdrachtgever)
        {
            _rekeningnummeropdrachtgever = rekeningnummeropdrachtgever;
            _gewensteverwerkingsdatum = Utility.ClieOpDatum(gewensteverwerkingsdatum);
            _naamopdrachtgever = naamopdrachtgever;
        }

        internal long Aantalposten { get; private set; }
        internal long Batchvolgnummer { get; set; }
        internal long Totaalbedrag { get; private set; }
        internal long Totaalrekeningnummers { get; private set; }
        internal TransactionFileType TransactionFileType { get; set; }

        private string Transactiegroep
        {
            get { return TransactionFileType == TransactionFileType.DirectDebit ? "10" : "00"; }
        }

        /// <summary>
        /// A list of transaction items.
        /// </summary>
        public IList<Post> Posten
        {
            get { return _posten; }
        }

        /// <summary>
        /// Contains a description that applies to all items in the batch.
        /// </summary>
        public IList<string> Vasteomschrijvingen
        {
            get { return _vasteomschrijvingen; }
        }

        /// <summary>
        /// Add a new transaction item to `Posten'.
        /// </summary>
        /// <param name="post">The transaction item to add to the Batch.</param>
        /// <exception cref="ArgumentException">Cannot add `Post'; maximum is 9999.</exception>
        /// <exception cref="ArgumentException">Cannot add business payment transaction to direct debit batch.</exception>
        /// <exception cref="ArgumentException">Cannot add direct debit transaction to business payment batch.</exception>
        public void AddPost(Post post)
        {
            if (_posten.Count >= 9999)
            {
                throw new ArgumentException("Cannot add `Post'; maximum is 9999.");
            }
            if (post.PostPaymentType != Post.PaymentType.DirectDebit && TransactionFileType == TransactionFileType.DirectDebit)
            {
                throw new ArgumentException("Cannot add business payment transaction to direct debit batch.");
            }
            if (post.PostPaymentType == Post.PaymentType.DirectDebit && TransactionFileType != TransactionFileType.DirectDebit)
            {
                throw new ArgumentException("Cannot add direct debit transaction to business payment batch.");
            }
            if (post.Rekeningnummerbegunstigde == 0 && post.PostPaymentType == Post.PaymentType.DirectDebit)
            {
                post.Rekeningnummerbegunstigde = _rekeningnummeropdrachtgever;
            }
            if (post.Rekeningnummerbetaler == 0 && post.PostPaymentType != Post.PaymentType.DirectDebit)
            {
                post.Rekeningnummerbetaler = _rekeningnummeropdrachtgever;
            }
            Totaalbedrag += post.Bedrag;
            Totaalrekeningnummers += post.Rekeningnummerbetaler;
            Totaalrekeningnummers += post.Rekeningnummerbegunstigde;
            Aantalposten++;
            _posten.Add(post);
        }

        /// <summary>
        /// Add fixed description to the Batch.
        /// </summary>
        /// <param name="vasteomschrijving">A string containing the fxed description.</param>
        /// <exception cref="ArgumentException">Cannot add `Vasteomschrijving'; maximum is 4.</exception>
        public void AddVasteomschrijving(string vasteomschrijving)
        {
            if (_vasteomschrijvingen.Count >= 4)
            {
                throw new ArgumentException("Cannot add `Vasteomschrijving'; maximum is 4.");
            }
            _vasteomschrijvingen.Add(vasteomschrijving);
        }

        /// <summary>
        /// Serialize the Batch to a string. The resulting string conforms to the ClieOp specification.
        /// </summary>
        /// <returns>The Batch as a string.</returns>
        public new string ToString()
        {
            var sb = new StringBuilder();
            sb.Append(
                new Batchvoorlooprecord
                    {
                        Aanleveringsmuntsoort = Aanleveringsmuntsoort,
                        Batchvolgnummer = Batchvolgnummer,
                        Rekeningnummeropdrachtgever = _rekeningnummeropdrachtgever,
                        Transactiegroep = Transactiegroep
                    }.ToString());
            foreach (string oms in Vasteomschrijvingen)
            {
                sb.Append(new Vasteomschrijvingsrecord {Vasteomschrijving = oms}.ToString());
            }
            sb.Append(
                new Opdrachtgeverrecord
                    {Gewensteverwerkingsdatum = _gewensteverwerkingsdatum, Naamopdrachtgever = _naamopdrachtgever, Nawcode = Nawcode, Testcode = Testcode}.
                    ToString());
            foreach (Post post in Posten)
            {
                sb.Append(post.ToString());
            }
            sb.Append(new Batchsluitrecord {Aantalposten = Aantalposten, Totaalbedrag = Totaalbedrag, Totaalrekeningnummers = Totaalrekeningnummers}.ToString());
            return sb.ToString();
        }

        internal static Batch Parse(ClieOpReader reader)
        {
            Batch batch = null;
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Batchvoorlooprecord)
            {
                long rekeningnummeropdrachtgever = ((Batchvoorlooprecord)reader.CurrentRecord).Rekeningnummeropdrachtgever;
                reader.MoveToNext();
                var vasteomschrijvingen = new List<string>();
                while (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Vasteomschrijvingsrecord)
                {
                    vasteomschrijvingen.Add(((Vasteomschrijvingsrecord)reader.CurrentRecord).Vasteomschrijving.TrimEnd());
                    reader.MoveToNext();
                }
                if (reader.CurrentRecord == null || reader.CurrentRecord.RecordType != RecordType.Opdrachtgeverrecord)
                {
                    throw new Exception(string.Format("Missing Opdrachtgeverrecord. Line number {0}.", reader.LineNumber));
                }
                var opdrachtgeverrecord = (Opdrachtgeverrecord)reader.CurrentRecord;
                DateTime gewensteverwerkingsdatum = Utility.ClieOpDatumToDateTime((int)opdrachtgeverrecord.Gewensteverwerkingsdatum);
                string naamopdrachtgever = opdrachtgeverrecord.Naamopdrachtgever;
                reader.MoveToNext();
                batch = new Batch(rekeningnummeropdrachtgever, gewensteverwerkingsdatum, naamopdrachtgever);
                foreach (string vasteomschrijving in vasteomschrijvingen)
                {
                    batch.AddVasteomschrijving(vasteomschrijving);
                }
                Post post = Post.Parse(reader);
                while (post != null)
                {
                    batch.AddPost(post);
                    post = Post.Parse(reader);
                }
                if (reader.CurrentRecord == null || reader.CurrentRecord.RecordType != RecordType.Batchsluitrecord)
                {
                    throw new Exception(string.Format("Missing Batchsluitrecord. Line number {0}.", reader.LineNumber));
                }
                var batchsluitrecord = (Batchsluitrecord)reader.CurrentRecord;
                if (batchsluitrecord.Aantalposten != batch.Aantalposten)
                {
                    throw new Exception(String.Format("Aantalposten in ClieOp file does not match the actual Aantalposten. Expected: {0}, actual: {1}.",
                                                      batchsluitrecord.Aantalposten, batch.Aantalposten));
                }
                if (batchsluitrecord.Totaalbedrag != batch.Totaalbedrag)
                {
                    throw new Exception(String.Format("Totaalbedrag in ClieOp file does not match the actual Totaalbedrag. Expected: {0}, actual: {1}.",
                                                      batchsluitrecord.Totaalbedrag, batch.Totaalbedrag));
                }
                if (batchsluitrecord.Totaalrekeningnummers != batch.Totaalrekeningnummers)
                {
                    throw new Exception(
                        String.Format("Totaalrekeningnummers in ClieOp file does not match the actual Totaalrekeningnummers. Expected: {0}, actual: {1}.",
                                      batchsluitrecord.Totaalrekeningnummers, batch.Totaalrekeningnummers));
                }
                reader.MoveToNext();
            }
            return batch;
        }
    }
}